/*
 * Copyright (C) 2023 razaghimahdi (Mahdi Razzaghi Ghaleh)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitee.wsl.compose.ui.drawer

import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.DecayAnimationSpec
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.animate
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import com.gitee.wsl.compose.animation.A
import com.gitee.wsl.compose.animation.spec.Spac
import com.gitee.wsl.compose.ui.base.Drawer
import com.gitee.wsl.ext.base.ifValue
import kotlinx.coroutines.CancellationException


@Composable
fun CardDrawerState.state(initialValue: DrawerValue = DrawerValue.Closed,
                     confirmStateChange: (DrawerValue) -> Boolean = { true })
     = rememberCardDrawerState(initialValue = initialValue,confirmStateChange)

@Composable
fun rememberCardDrawerState(initialValue: DrawerValue = DrawerValue.Closed,confirmStateChange: (DrawerValue) -> Boolean = { true }): CardDrawerState {
    return rememberSaveable(saver = CardDrawerState.Saver(confirmStateChange)) {
        CardDrawerState(initialValue = initialValue)
    }
}

class CardDrawerState(
    initialValue:DrawerValue,
    confirmStateChange: (DrawerValue) -> Boolean = { true },
    animationSpec: TweenSpec<Float> = TweenSpec(durationMillis = 500),
) {

    internal val DrawerVelocityThreshold = 400.dp
    private var AnimationSpec = animationSpec

    internal var density: Density? by mutableStateOf(null)

    private fun requireDensity() = requireNotNull(density) {
        "The density on BottomDrawerState ($this) was not set. Did you use BottomDrawer" +
                " with the BottomDrawer composable?"
    }

    @OptIn(ExperimentalFoundationApi::class)
    internal val swipeableState = AnchoredDraggableState(
        initialValue = initialValue,
        anchors = DraggableAnchors{ },
        positionalThreshold = { totalDistance:Float -> totalDistance * 0.5f },
        snapAnimationSpec = AnimationSpec,
        decayAnimationSpec = exponentialDecay(),
        confirmValueChange =  confirmStateChange,
        velocityThreshold = { with(requireDensity()) { DrawerVelocityThreshold.toPx() } },
    )

    /**
     * The current position (in pixels) of the drawer sheet.
     */
    @OptIn(ExperimentalFoundationApi::class)
    @Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
    val currentOffset: Float
        get() = swipeableState.offset.run { isNaN().ifValue(0f,this) }

    /**
     * The current value of the state.
     *
     * If no swipe or animation is in progress, this corresponds to the start the drawer
     * currently in. If a swipe or an animation is in progress, this corresponds the state drawer
     * was in before the swipe or animation started.
     */
    @OptIn(ExperimentalFoundationApi::class)
    val currentValue: DrawerValue
        get() {
            return swipeableState.currentValue
        }

    /**
     * Whether the drawer is open, either in opened or expanded state.
     */
    val isOpen: Boolean
        get() = currentValue != DrawerValue.Closed

    /**
     * Whether the drawer is closed.
     */
    val isClosed: Boolean
        get() = currentValue == DrawerValue.Closed

    /**
     * Open the drawer with animation and suspend until it if fully opened or animation has been
     * cancelled. This method will throw [CancellationException] if the animation is
     * interrupted
     *
     * @return the reason the open animation ended
     */
    suspend fun open() = animateTo(DrawerValue.Open, AnimationSpec)

    /**
     * Close the drawer with animation and suspend until it if fully closed or animation has been
     * cancelled. This method will throw [CancellationException] if the animation is
     * interrupted
     *
     * @return the reason the close animation ended
     */
    suspend fun close() = animateTo(DrawerValue.Closed, AnimationSpec)

    /**
     * Set the state of the drawer with specific animation
     *
     * @param targetValue The new value to animate to.
     * @param anim The animation that will be used to animate to the new value.
     */
   /* @OptIn(ExperimentalFoundationApi::class)
    suspend fun animateTo(targetValue: DrawerValue, anim: AnimationSpec<Float>) {
        swipeableState.animateTo(targetValue, anim)
    }*/

    @OptIn(ExperimentalFoundationApi::class)
    private suspend fun animateTo(
        targetValue: DrawerValue,
        animationSpec: AnimationSpec<Float> = AnimationSpec,
        velocity: Float = swipeableState.lastVelocity
    ) {
        swipeableState.anchoredDrag(targetValue = targetValue) { anchors, latestTarget ->
            val targetOffset = anchors.positionOf(latestTarget)
            if (!targetOffset.isNaN()) {
                var prev = if (currentOffset.isNaN()) 0f else currentOffset
                animate(prev, targetOffset, velocity, animationSpec) { value, velocity ->
                    // Our onDrag coerces the value within the bounds, but an animation may
                    // overshoot, for example a spring animation or an overshooting interpolator
                    // We respect the user's intention and allow the overshoot, but still use
                    // DraggableState's drag for its mutex.
                    dragTo(value, velocity)
                    prev = value
                }
            }
        }
    }

    companion object {
        /**
         * The default [Saver] implementation for [DrawerState].
         */
        fun Saver(confirmStateChange: (DrawerValue) -> Boolean) =
            Saver<CardDrawerState, DrawerValue>(
                save = { it.currentValue },
                restore = { CardDrawerState(it, confirmStateChange) }
            )
    }
}